/*
Copyright (c) 2004 Mike Ellison.  All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 
	
  * Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
	and/or other materials provided with the distribution.
	
  * The name of the author may not be used to endorse or promote products 
    derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

using System;
using System.IO;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Xml;
using System.Text.RegularExpressions;

namespace UNLV.IAP.WebControls
{
/// <summary>
/// A user interface web control for building conditions suitable for
/// use in a SQL WHERE clause
/// </summary>
/// <remarks>
/// <para>
/// <b>SqlWhereBuilder</b> is a web control which provides a user interface for generating custom SQL WHERE clauses.  Users add <i>conditions</i> through the interface, and developers use either the <see cref="GetWhereClause">GetWhereClause()</see> or <see cref="GetWhereClauseWithParameters">GetWhereClauseWithParameters()</see> methods upon postback to compile the chosen conditions into a string of text suitable for inclusion in a SQL WHERE clause.
/// </para>
/// <para>Users add conditions through a client-side entry form which consists of the following parts:
/// <ul>
/// <li><i>Field Dropdown</i><br />The dropdown provides a listing of Fields; each condition begins with a Field</li>
/// <li><i>Operators Dropdown</i><br />When a field is selected, the Operators dropdown provides a listing of <i>operators</i> appropriate for the field.  For example, a text field may contain the operators "Is", "Is Not", "Contains", and "Is Null".  A numeric operator may contain operators such as "Equals" and "Is Greater Than".  When configured by a developer, operators have associated <i>sqlTemplates</i> which are used to translate the a condition to a syntax appropriate for a SQL WHERE clause.</li>
/// <li><i>ValueEntry area</i><br />When an operator is selected, its associated <i>ValueEntry</i> area is displayed, providing the user with the means to enter comparison value(s) appropriate for the operator.  ValueEntry areas are translated as literal &lt;div&gt; tags that are displayed and hidden on the client-side as operators are selected.</li>
/// <li><i>And/Or Dropdown</i><br />The And/Or dropdown appears at the beginning of the entry form when at least one condition has already been added.  Users may specify standard SQL <i>and</i>s or <i>or</i>s in between multiple conditions.</li>
/// </ul>
/// </para>
/// <para>
/// Though SqlWhereBuilder is a server-side control, user-interaction is handled completely client-side through a custom JavaScript library.  This prevents the need for server postbacks upon the manipulation of conditions.  To use the control, the file <i>SqlWhereBuilder.js</i> must be present in an expected location.  By default, this location is the following:<code><i>wwwroot</i>\aspnet_client\UNLV_IAP_WebControls\SqlWhereBuilder\</code>where <i>wwwroot</i> is the root folder of the web server.  To identify the file in a different location, set the <see cref="ClientCodeLocation">ClientCodeLocation</see> property to the full virtual path of the <i>SqlWhereBuilder.js</i> JavaScript file.
/// </para>
/// <para>
/// Configuration of the SqlWhereBuilder is typically done through XML files; the configuration files are identified through the properties <see cref="ValueEntryFile">ValueEntryFile</see>, <see cref="OperatorListsFile">OperatorListsFile</see>, and <see cref="FieldsFile">FieldsFile</see>.   Though the flexibility is there to supply these as individual files, that isn't strictly necessary; all configuration tags may appear in a single file and that file identified in each of the three properties if desired.
/// </para>
/// <para>
/// <i>ValueEntry</i> areas may contain HTML and plain text.  Form inputs supported include &lt;input&gt; tags of type "text" and "radio" and &lt;select&gt; tags.  Alternatively, a ValueEntry area may be derived from an ASP.NET <see cref="UserControl">UserControl</see>, provided that UserControl renders HTML among the supported form inputs (text boxes, radio buttons, and dropdown lists).  ValueEntry areas are defined using &lt;valueEntry&gt; tags with the following attributes:
/// <ul>
/// <li><i>id</i> - a unique identifier for this ValueEntry area, for reference by an <i>operator</i>
/// </li>
/// <li><i>userControl</i> - (optional) the virtual path of a UserControl (.ascx) to render for this ValueEntry area.
/// </li>
/// </ul>
/// This example shows a <see cref="ValueEntryFile">ValueEntryFile</see> with four entry areas defined:  one for a single text box, one blank (for operators where additional user entry is not required), one with a dropdown box for selections, and one defined by an external UserControl.
/// <code>
/// <![CDATA[
/// <configuration>
/// 
/// 	<valueEntry id="onetext">
/// 		<input type="text" id="onetext_1" size="6" />
/// 	</valueEntry>
/// 	
/// 	<valueEntry id="blank">
/// 	    <!-- left intentionally blank -->
/// 	</valueEntry>
/// 	
/// 	<valueEntry id="region">
/// 	    <select id="region_select1">
/// 	        <option value="N">North</option>
/// 	        <option value="S">South</option>
/// 	        <option value="E">East</option>
/// 	        <option value="W">West</option>	        
/// 	    </select>
/// 	</valueEntry>
/// 	
/// 	<valueEntry id="customers" userControl="CustomersDropdown.ascx" />
/// 	
/// </configuration>
/// ]]>
/// </code>
/// When defining ValueEntry areas, it is imported to provide each form input with an <i>id</i> attribute (such as "onetext_1" for the "onetext" entry in the example above).  Form input <i>id</i>s are referenced by the <i>sqlTemplate</i> attributes of operators.
/// </para>
/// <para>
/// <i>Operators</i> are grouped into <i>OperatorLists</i>, defined through &lt;operator&gt; and &lt;operatorList&gt; tags respectively.  An OperatorList provides a list of operators appropriate for a given field.  OperatorLists may be thought of as loosely tied to a specific datatype (such as a text, numeric, or date datatype) and would provide appropriate operator choices for fields of the given datatype.  But customized OperatorLists may also be defined, for example, to limit choices for a stock datatype, or to provide custom choices appropriate to a userControl ValueEntry area.  OperatorLists have a single attribute:
/// <ul>
/// <li><i>id</i> - a unique identifier for this OperatorList, for reference by a <i>field</i>
/// </li>
/// </ul>
/// Operators are defined with the following attributes:
/// <ul>
/// <li><i>id</i> - a unique identifier for this operator
/// </li>
/// <li><i>text</i> - display text for the Operators dropdown in the SqlWhereBuilder entry form
/// </li>
/// <li><i>valueEntry</i> - the <i>id</i> of the associated ValueEntry area; when this operator is selected from the dropdown, the associated ValueEntry area is displayed as well.
/// </li>
/// <li><i>sqlTemplate</i> - a template string for defining how a condition using this operator will translate into appropriate SQL syntax.
/// </li>
/// </ul>
/// The <i>sqlTemplate</i> attribute uses placeholders in an otherwise SQL-compliant condition.  The literal placeholder <i>#FIELD#</i> substitutes for the field in the condition.  Form inputs in ValueEntry areas are represented using placeholders derived from the input <i>id</i> attribute (or the <i>name</i> attribute, in the case of radio button groups), using pound signs (#) as delimiters.  For example, if the ValueEntry area defines a text input id="onetext_1", the placeholder in the <i>sqlTemplate</i> would be <i>#onetext_1#</i>.
/// </para>
/// <para>
/// One additional consideration is important when designing <i>sqlTemplate</i>s:  whether or not the WHERE clause will be constructed as a literal string (using the <see cref="GetWhereClause">GetWhereClause()</see> method) or as a string with parameter placeholders (using the <see cref="GetWhereClauseWithParameters">GetWhereClauseWithParameters() method</see>).  If using the former method, then appropriate delimiters for datatypes (single quotes for text types, for example) should be included in the <i>sqlTemplate</i>.  If using the latter method, then datatype delimiters would not be necessary; value entry input placeholders would be replaced with IDbDataParameter placeholders as appropriate within the compiled WHERE clause.  The following example shows an equals comparison operator for a text datatype using the former method, incorporating single quotes for text delimiters:
/// <code>
/// <![CDATA[
/// 		<operator id="text_is" text="Is" valueEntry="onetext" 
/// 		          sqlTemplate="#FIELD# = '#onetext_1#'" />
/// ]]>
/// </code>
/// The same operator defined for use with IDbDataParameter objects (the GetWhereClauseWithParameters() method) would look like this (no single quotes for delimiters):
/// <code>
/// <![CDATA[
/// 		<operator id="text_is" text="Is" valueEntry="onetext" 
/// 		          sqlTemplate="#FIELD# = #onetext_1#" />
/// ]]>
/// </code>
/// </para>
/// <para>
/// The following example shows an <see cref="OperatorListsFile">OperatorListsFile</see> with five lists defined:  one for generic text datatypes, one for boolean conditions, one for numeric datatypes, one for a region selection, and one making use of the "customers" ValueEntry area defined in the ValueEntry example above.  These operators assume that the GetWhereClauseWithParameters() method will be used to compile the WHERE clause, so datatype delimiters are not used.
/// <code>
/// <![CDATA[
/// <configuration>
/// 	<operatorList id="opList_text">
/// 		<operator id="opList_text_is" text="Is" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# = #onetext_1#" />
/// 		          
/// 		<operator id="opList_text_isnot" text="Is Not" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# != #onetext_1#" />
/// 
/// 		<operator id="opList_text_isnull" text="Is Null" valueEntry="blank"
/// 		          sqlTemplate="#FIELD# IS NULL" />
/// 	</operatorList>
/// 	
/// 	<operatorList id="opList_boolean">
/// 	    <operator id="opList_boolean_true" text="Is True" valueEntry="blank"
/// 	              sqlTemplate="#FIELD# = 1" />
/// 	
/// 	    <operator id="opList_boolean_false" text="Is False" valueEntry="blank"
/// 	              sqlTemplate="#FIELD# = 0" />
/// 
/// 	    <operator id="opList_boolean_null" text="Is Null" valueEntry="blank"
/// 	              sqlTemplate="#FIELD# IS NULL" />
/// 
/// 	    <operator id="opList_boolean_notnull" text="Is Not Null" valueEntry="blank"
/// 	              sqlTemplate="#FIELD# IS NOT NULL" />
/// 	</operatorList>
/// 	
/// 	<operatorList id="opList_numeric">
/// 		<operator id="opList_numeric_equals" text="Equals" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# = #onetext_1#" />
/// 		          
/// 		<operator id="opList_numeric_notequals" text="Does Not Equal" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# != #onetext_1#" />
/// 
/// 		<operator id="opList_numeric_gt" text="Is Greater Than" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# &gt; #onetext_1#" />
/// 
/// 		<operator id="opList_numeric_lt" text="Is Less Than" valueEntry="onetext"
/// 		          sqlTemplate="#FIELD# &lt; #onetext_1#" />
/// 	</operatorList>
/// 	
/// 	<operatorList id="opList_region">
/// 	    <operator id="opList_region_is" text="Is" valueEntry="region"
/// 	              sqlTemplate="#FIELD# = #region_select1#" />
/// 	
/// 	    <operator id="opList_region_isnot" text="Is Not" valueEntry="region"
/// 	              sqlTemplate="#FIELD# != #region_select1#" />
/// 	</operatorList>
/// 	
/// 	<operatorList id="opList_customers">
/// 	    <operator id="opList_customers_is" text="Is" valueEntry="customers"
/// 	              sqlTemplate="#FIELD# = #customers_ddCustomers#" />
/// 	
/// 	    <operator id="opList_customers_isnot" text="Is Not" valueEntry="customers"
/// 	              sqlTemplate="#FIELD# != #customers_ddCustomers#" />
/// 	</operatorList>
/// 	
/// </configuration>
/// ]]>
/// </code>
/// </para>
/// <para>
/// <i>Fields</i> are defined through &lt;field&gt; tags with the following attributes:
/// <ul>
/// <li><i>id</i> - the unique identifier for this field; the <i>id</i> should be the same as the field name in the database
/// </li>
/// <li><i>text</i> - display text for the Fields dropdown in the SqlWhereBuilder entry form
/// </li>
/// <li><i>operatorList</i> - the <i>id</i> of the associated OperatorList; when this field is selected in the entry form, the Operators dropdown is populated with the group of <i>operators</i> defined by the <i>operatorList</i>.
/// </li>
/// <li><i>parameterDataType</i> - the <see cref="System.Data.DbType">System.Data.DbType</see> of the <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object to incorporate when using the <see cref="GetWhereClauseWithParameters">GetWhereClauseWithParameters()</see> method; if using <see cref="GetWhereClause">GetWhereClause()</see> instead, this attribute is not necessary. 
/// </li>
/// </ul>
/// The following example shows a <see cref="FieldsFile">FieldsFile</see> with six fields defined, making use of the <i>operatorLists</i> defined above:
/// <code>
/// <![CDATA[
/// <configuration>
/// 
/// 	<field id="Text1" text="My First Text Field"    operatorList="opList_text"			
/// 	       parameterDataType="String" />
/// 	       
/// 	<field id="Bool1" text="My Boolean Field"       operatorList="opList_boolean"		
///            parameterDataType="Boolean"/>
/// 	
/// 	<field id="Region1" text="My Region"            operatorList="opList_region"		
/// 	       parameterDataType="String" />
/// 	
/// 	<field id="Text2"  text="My Second Text Field"  operatorList="opList_text"			
/// 	       parameterDataType="String" />
/// 	
/// 	<field id="Customer" text="Customer"            operatorList="opList_customers"	
/// 	       parameterDataType="String" />
/// 	
/// 	<field id="IntField" text="My Integer Field"    operatorList="opList_numeric"       
/// 	       parameterDataType="Int16" />
/// 	       
/// </configuration>
/// 
/// ]]>
/// </code>
/// </para>
/// <para>
/// The SqlWhereBuilder control fires the <see cref="ConditionsChanged">ConditionsChanged</see> event if the conditions have been changed by the user between posts back to the server.  The posted conditions may be inspected through the <see cref="Conditions">Conditions</see> property if desired.
/// </para>
/// <para>
/// To compile a SQL WHERE clause from the posted set of conditions, the developer may use either the <see cref="GetWhereClause">GetWhereClause()</see> or <see cref="GetWhereClauseWithParameters">GetWhereClauseWithParameters()</see> methods.  Both return a SQL-syntax string compiled using the <i>sqlTemplate</i> attributes of the operator for each supplied condition.  The string is returned without the word "WHERE" to allow for flexibility in its use.
/// </para>
/// <para>
/// If using GetWhereClause(), note that <i>sqlTemplate</i> attributes for operator objects must be defined using appropriate datatype delimiters (such as single quotes for character types).  Also note that this method may be prone to SQL injection-type attacks.  Although the control attempts to mitigate that possibility by calling the <see cref="ValidateValue">ValidateValue()</see> method for each submitted value, the developer may wish to perform his or her own validation on the returned string.  The ValidateValue method is defined as virtual to allow developers to override this method if desired.
/// </para>
/// <para>
/// The GetWhereClauseWithParameters() method may be used to return a WHERE clause string designed for use with an <see cref="System.Data.IDbCommand">IDbCommand</see> object.  If using a SqlCommand, OleDbCommand, or OdbcCommand object, use the following overload:
/// <code>
/// // assumes cmd is either a SqlCommand, OleDbCommand, or OdbcCommand object
/// string sWhere = SqlWhereBuilder1.GetWhereClauseWithParameters(cmd);
/// </code>
/// This method will return the WHERE clause with parameter placeholders appropriate to the IDbCommand type (@params for a SqlCommand, or ? for OleDbCommand and OdbcCommand types) and populate the Parameters collection of the IDbCommand object with matching IDbDataParameter objects.  If using an IDbCommand object of a different type, use the following overload:
/// <code>
/// // assumes cmd is NOT a SqlCommand, OleDbCommand, nor OdbcCommand object
/// string sWhere = SqlWhereBuilder1.GetWhereClauseWithParameters(cmd, sPlaceholderTemplate, sNameTemplate);
/// </code>
/// This allows the developer to supply a parameter placeholder template and a parameter name template appropriate to the IDbCommand type.  The templates are parsed with String.Format and should include "{0}" to substitute for the parameter positional number.  For example, the OracleCommand object from the System.Data.OracleClient namespace expects parameter placeholders in its CommandText property to be in the form of ":paramName", with a colon prefix on each.  The parameter names would not include the colon.  So a call to GetWhereClauseWithParameters() for an OracleCommand would look like this:
/// <code>
/// // assumes cmd is an OracleCommand object
/// string sWhere = SqlWhereBuilder1.GetWhereClauseWithParameters(cmd, ":param{0}", "param{0}");
/// </code>
/// Once the WHERE clause is retrieved, it may be appended to the CommandText property of the IDbCommand object, and the query may be executed.  The following example shows the execution of such a query following a button click.  In this example, <i>dgResults</i> is a DataGrid control on the page, and <i>SqlWhereBuilder1</i> is a SqlWhereBuilder control:
/// <code>
/// <![CDATA[
/// private void Button1_Click(object sender, System.EventArgs e)
/// {
/// 
///     OleDbConnection con = null;
///     OleDbCommand cmd = null;
///     OleDbDataAdapter da = null;
///     DataSet ds = new DataSet();
/// 
/// 	try
/// 	{
/// 	    // GetConnection() is a method defined elsewhere which returns an OleDbConnection
/// 	    // object
/// 	    con = GetConnection();
/// 	    cmd = new OleDbCommand("SELECT * FROM MyTable", con);
/// 	    
/// 	    // inspect the SqlWhereBuilder.Conditions property to see if any conditions
/// 	    // were supplied
/// 	    if (SqlWhereBuilder1.Conditions.Count > 0)
/// 	    {
/// 	        // retrieve the WHERE clause and add parameters to the OleDbCommand object
/// 	        string sWhere = SqlWhereBuilder1.GetWhereClauseWithParameters(cmd);
/// 	        
/// 	        // add the WHERE clause to the command text; we could throw in additional
/// 	        // WHERE clause criteria here if we wanted
/// 	        cmd.CommandText += " WHERE " + sWhere;
/// 	    }
/// 	
/// 	    // execute the query
/// 	    da = new OleDbDataAdapter(cmd);
/// 	    da.Fill(ds);
/// 	    
/// 	    // bind results to our datagrid
/// 	    dgResults.DataSource = ds;
/// 		dgResults.DataBind();
/// 
/// 	}
/// 	catch (Exception ex)
/// 	{
/// 		// do something with exceptions...
/// 	}
/// 	finally
/// 	{
/// 	    if (ds != null) ds.Dispose();
/// 	    if (da != null) da.Dispose();
/// 	    if (cmd != null) cmd.Dispose();
/// 	    if (con != null) con.Dispose();
/// 	}
/// }
/// ]]>
/// </code>
/// </para>
/// <para>
/// A number of CSS class and style properties are available to modify the appearance of the SqlWhereBuilder control.  Look at the SqlWhereBuilder control members for a complete listing.
/// </para>
/// </remarks>
/// <example>
/// The following shows an example of declaring the SqlWhereBuilder control on an .aspx page.
/// <code>
/// <![CDATA[
/// <%@ Register TagPrefix="cc1" Namespace="UNLV.IAP.WebControls" Assembly="SqlWhereBuilder" %>
/// 
/// <html>
///   <head>
///     <title>SqlWhereBuilder example</title>
///   </head>
///   
///   <body>
///     <form runat="server">
///       <h3>SqlWhereBuilder example</h3>
///       <cc1:SqlWhereBuilder id="SqlWhereBuilder1" runat="server"
///                            FieldsFile="fields.config"
///                            OperatorListsFile="operatorLists.config"
///                            ValueEntryFile="valueEntry.config"         
///                            />
///     </form>
///   </body>
/// </html>  
/// ]]>
/// </code>
/// </example>
	[DefaultEvent("ConditionsChanged")
	,ToolboxData("<{0}:SqlWhereBuilder runat=server></{0}:SqlWhereBuilder>")
	,Designer(typeof(SqlWhereBuilderDesigner))
	]
	public class SqlWhereBuilder : System.Web.UI.Control, IPostBackDataHandler
	{

		#region Constants
		private const string kLOCATION_JSCLIENTCODE = "/aspnet_client/UNLV_IAP_WebControls/SqlWhereBuilder/SqlWhereBuilder.js";

		private const string kHIDDEN_CONDITIONS = "conditionsXml";

		private const string kEXCEPTION_NOFIELDS = "The SqlWhereBuilder control id='{0}' requires at least one Field.";
		private const string kEXCEPTION_MISSINGOPLIST = "The SqlWhereBuilder control id='{0}' has a field id='{1}' with an operators list '{2}' that cannot be found.";
		private const string kEXCEPTION_MISSINGVALUEENTRYDIV = "The SqlWhereBuilder control id='{0}' has an operators list id='{1}', operator '{2}' with a ValueEntry '{3}' that cannot be found.";
		private const string kEXCEPTION_FIELDDOESNTEXIST = "The SqlWhereBuilder control id='{0}' has a condition with FieldId='{1}'; a Field with that Id cannot be found.";
		private const string kEXCEPTION_OPDOESNTEXIST = "The SqlWhereBuilder control id='{0}' has a condition with OperatorId='{1}'; an Operator with that Id cannot be found.";

		#endregion

		#region Custom object collection properties

		/// <summary>
		/// Collection of ValueEntry divs available to be referenced by operators.
		/// </summary>
		/// <remarks>
		/// Though typically ValueEntry divs are defined through an XML configuration
		/// file, you may manually add ValueEntry divs to the collection with the
		/// <see cref="SqlWhereBuilderValueEntryDivCollection.Add">Add</see> method.
		/// </remarks>
		/// <example>
		/// This example shows how to manually add ValueEntry divs to the SqlWhereBuilder's
		/// ValueEntryDivs collection.  The first added div is a single textbox.  The
		/// second is defined through an .ascx UserControl.
		/// <code>
		/// <![CDATA[
		///private void Page_Load(object sender, System.EventArgs e)
		///{
		///	if (!IsPostBack)
		///	{
		///
		///		// Manually add a single-text-box valueEntry div
		///		SqlWhereBuilder1.ValueEntryDivs.Add(
		///			new SqlWhereBuilderValueEntryDiv(
		///				"valueEntry_singleText",
		///				"<input type=\"text\" id=\"singleText1\" />"
		///			));
		///    
		///		// Manually add a valueEntry div created from UserControl.
		///		SqlWhereBuilder1.ValueEntryDivs.Add(
		///			new SqlWhereBuilderValueEntryDiv(
		///				"valueEntry_regions",
		///				"RegionsUserControl.ascx",
		///				true
		///			));
		///    }
		///}
		/// ]]>
		/// </code>
		/// </example>
		/// <seealso cref="ValueEntryFile">ValueEntryFile xml configuration</seealso>
		[Browsable(false)]
		public SqlWhereBuilderValueEntryDivCollection ValueEntryDivs
		{
			get
			{
				SqlWhereBuilderValueEntryDivCollection sd;
				Object o = ViewState["ValueEntryDivs"];
				if (o == null)
				{
					sd = new SqlWhereBuilderValueEntryDivCollection();
					ViewState["ValueEntryDivs"] = (Object) sd;
				}
				else
				{
					sd = (SqlWhereBuilderValueEntryDivCollection) o;
				}

				return sd;
			}
		}


		/// <summary>
		/// Arraylist of <see cref="SqlWhereBuilderOperatorsCollection">SqlWhereBuilderOperatorsCollection</see>
		/// objects available to the Fields of this SqlWhereBuilder
		/// </summary>
		/// <remarks>
		/// Each Field in the SqlWhereBuilder is associated to
		/// an OperatorsCollection by the OperatorsCollection Id property.
		/// Typically the OperatorLists property is configured through an XML file,
		/// but lists may be manually added through the <b>Add</b> method.
		/// </remarks>
		/// <example>
		/// The following example shows how to set up an operators list in
		/// the Page_Load event.  
		/// <code><![CDATA[
		///private void Page_Load(object sender, System.EventArgs e)
		///{
		///	  if (!IsPostBack)
		///	  {
		///	    // Create the ValueEntry divs to be used by the operator list...
		///	    // ...
		///	    
		///		// Create an OperatorList for a text field
		///		SqlWhereBuilderOperatorsCollection ol 
		///			= new SqlWhereBuilderOperatorsCollection("text");
		///
		///		ol.Add(new SqlWhereBuilderOperator
		///			("text_equals", "Equals", "valueEntry_singleText", "#FIELD# = '#singleText1#'"));
		///		ol.Add(new SqlWhereBuilderOperator
		///			("text_notequals", "Does not equal", "valueEntry_singleText", "#FIELD# != '#singleText1#'"));
		///		ol.Add(new SqlWhereBuilderOperator
		///			("text_isnull", "Is Null", "valueEntry_blank", "#FIELD# IS NULL"));
		///	
		///		SqlWhereBuilder1.OperatorLists.Add(ol);
		///    }
		///}		
		/// ]]></code>
		/// </example>
		/// <seealso cref="SqlWhereBuilderOperatorsCollection">SqlWhereBuilderOperatorsCollection</seealso>
		/// <seealso cref="SqlWhereBuilderOperator">SqlWhereBuilderOperator</seealso>
		/// <seealso cref="SqlWhereBuilderValueEntryDivCollection">SqlWhereBuilderValueEntryDivCollection</seealso>
		[Browsable(false)]
		public ArrayList OperatorLists
		{
			get 
			{
				ArrayList sd;
				Object o = ViewState["OperatorLists"];
				if (o == null)
				{
					sd = new ArrayList();
					ViewState["OperatorLists"] = (Object) sd;
				}
				else
				{
					sd = (ArrayList) o;
				}

				return sd;
			}
		}


		/// <summary>
		/// Arraylist of data fields available for use by this SqlWhereBuilder
		/// </summary>
		/// <remarks>
		/// The Fields collection contains the items that will be displayed
		/// in the Fields dropdown of the SqlWhereBuilder.  These become the
		/// fields available for inclusion by the user in custom WHERE clause
		/// conditions.  Typically, the Fields property is set up using an XML-based
		/// configuration file, but fields may also be manually added in code
		/// with the <b>Add</b> method.
		/// </remarks>
		/// <example>
		/// The following example shows how to manually add fields 
		/// to the SqlWhereBuilder in the Page_Load event.
		/// <code>
		/// <![CDATA[
		///private void Page_Load(object sender, System.EventArgs e)
		///{
		///
		///  if (!IsPostBack)
		///  {
		///
		///    // create valueEntry divs...
		///    // ...
		///    
		///    // create OperatorLists... (one with id "text", one with id "trueFalse")
		///    // ...
		///
		///    // create each field; first is a text-type field
		///    SqlWhereBuilderField f;
		///    f = new SqlWhereBuilderField("Field1", "My First Field", "text");
		///    SqlWhereBuilder1.Fields.Add(f);
		///
		///    // create a boolean-type field
		///    f = new SqlWhereBuilderField("Field2", "My Second Field", "trueFalse");
		///    SqlWhereBuilder1.Fields.Add(f);
		///
		///  }
		///}
		/// ]]>
		/// </code>
		/// </example>
		[Browsable(false)]
		public ArrayList Fields
		{
			get
			{
				ArrayList sd;
				Object o = ViewState["Fields"];
				if (o == null)
				{
					sd = new ArrayList();
					ViewState["Fields"] = (Object) sd;
				}
				else
				{
					sd = (ArrayList) o;
				}

				return sd;
			}
		}


		/// <summary>
		/// Collection of WHERE clause conditions currently applied in 
		/// this SqlWhereBuilder
		/// </summary>
		/// <remarks>
		/// Typically the GetWhereClause or GetWhereClauseWithParameters
		/// methods would be used to compile all conditions into a SQL
		/// WHERE clause.  However, the Conditions collection may be
		/// iterated on a page postback to 
		/// to view individual conditions applied by the user.  The 
		/// Conditions collection may also be pre-populated 
		/// in the Page_Load event to supply
		/// the user with a default set of conditions.
		/// </remarks>
		[Browsable(false)]
		public SqlWhereBuilderConditionCollection Conditions
		{
			get
			{
				SqlWhereBuilderConditionCollection sd;
				Object o = ViewState["Conditions"];
				if (o == null)
				{
					sd = new SqlWhereBuilderConditionCollection();
					ViewState["Conditions"] = (Object) sd;
				}
				else
				{
					sd = (SqlWhereBuilderConditionCollection) o;
				}

				return sd;
			}
			set
			{
				ViewState["Conditions"] = (Object) value;
			}
		}

		#endregion

		#region Configuration properties
		private string _clientCodeLocation = kLOCATION_JSCLIENTCODE;
		private string _valueEntryFile = "";
		private string _operatorListsFile = "";
		private string _fieldsFile = "";

		/// <summary>
		/// Returns or sets the location of the client JavaScript code file
		/// </summary>
		/// <remarks>
		/// <para>The <b>ClientCodeLocation</b> path is the url written as the
		/// <b>src</b> attribute of a &lt;script&gt; tag as the control
		/// is rendered.  By default, this location is 
		/// <b>/aspnet_client/UNLV_IAP_WebControls/SqlWhereBuilder/SqlWhereBuilder.js</b>
		/// </para>
		/// <para>
		/// If the client javascript file is not in the location specified by 
		/// <b>ClientCodeLocation</b>, client-side javascript errors will occur.
		/// </para>
		/// </remarks>
		[Browsable(true), Category("Configuration"),
		 DefaultValue(kLOCATION_JSCLIENTCODE)]
		public string ClientCodeLocation
		{
			get {return _clientCodeLocation;}
			set {_clientCodeLocation = value;}
		}

		
		/// <summary>
		/// XML-based configuration file for defining ValueEntry divs
		/// </summary>
		/// <remarks>
		/// Set this property to the url of an xml file which defines
		/// the ValueEntry div areas to be used in this SqlWhereBuilder.
		/// ValueEntry areas render as standard HTML &lt;div&gt; tags and
		/// are displayed client-side as needed to support individual 
		/// operators.  They are
		/// defined in the xml file with &lt;valueEntry&gt; tags.
		/// <code>
		/// <![CDATA[
		///<valueEntry id="onetext">
		///  <input type="text" id="onetext_1" size="6" />
		///</valueEntry>
		/// ]]>
		/// </code>
		/// The above example defines a ValueEntry with id "onetext", and
		/// a single HTML text input for its contents.  HTML text and radio
		/// inputs are supported, as are &lt;select&gt; tags.  ValueEntry divs
		/// may also be defined using UserControls.  To identify a UserControl
		/// is the source for a valueEntry div in the xml file, use the
		/// <b>userControl</b> attribute:
		/// <code>
		/// <![CDATA[
		///<valueEntry id="regions" userControl="RegionSelect.ascx" />
		/// ]]>
		/// </code>
		/// </remarks>
		/// <seealso cref="SqlWhereBuilderValueEntryDiv">SqlWhereBuilderValueEntryDiv class</seealso>
		/// <seealso cref="SqlWhereBuilderValueEntryDivCollection">SqlWhereBuilderValueEntryDivCollection class</seealso>
		[Browsable(true), Category("Configuration")]
		public string ValueEntryFile
		{
			get{return _valueEntryFile;}
			set{_valueEntryFile = value;}
		}


		/// <summary>
		/// XML-based configuration file for defining OperatorLists
		/// </summary>
		/// <remarks>
		/// Set this property to the url of an xml file which defines the
		/// OperatorLists to be used by this SqlWhereBuilder.  An OperatorList
		/// defines the operators available when a given Field is selected.
		/// An OperatorList is defined with the &lt;operatorList&gt; tag and
		/// serves as a container for &lt;operator&gt; child tags.
		/// </remarks>
		/// <example>
		/// The following shows an example of an operatorLists configuration
		/// file with two lists defined - one for typical text datatypes, and
		/// one for typical date datatypes.  Note:  the <i>sqlTemplate</i>
		/// attributes here are set up for use with parameters; if parameters
		/// will not be used, then the <i>sqlTemplate</i>s would need to be
		/// modified to include, for example, single-quote text delimeters.
		/// <code>
		/// <![CDATA[
		///<configuration> 
		///    <operatorList id="datatype_text">
		///        <operator id="datatype_text_is" text="Is" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# = #onetext_1#" />
		///        <operator id="datatype_text_isnot" text="Is Not" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# != #onetext_1#" />
		///        <operator id="datatype_text_contains" text="Contains" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# LIKE '%' + #onetext_1# + '%' " />
		///        <operator id="datatype_text_doesnotcontain" text="Does Not Contain" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# NOT LIKE '%' + #onetext_1# + '%' "/>
		///        <operator id="datatype_text_startswith" text="Starts With" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# LIKE #onetext_1# + '%' " />
		///        <operator id="datatype_text_endswith" text="Ends With" valueEntry="onetext" 
		///                  sqlTemplate="#FIELD# LIKE '%' + #onetext_1#" />
		///        <operator id="datatype_text_isnull" text="Is Null" valueEntry="blank" 
		///                  sqlTemplate="#FIELD# IS NULL" />
		///        <operator id="datatype_text_isnotnull" text="Is Not Null" valueEntry="blank" 
		///                  sqlTemplate="#FIELD# IS NOT NULL" />
		///    </operatorList>
		///    
		///    
		///    <operatorList id="datatype_date">
		///        <operator id="datatype_date_ison" text="Is On" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# = #onedate_1#" />
		///        <operator id="datatype_date_isnoton" text="Is Not On" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# != #onedate_1#" />
		///        <operator id="datatype_date_isbetween" text="Is Between" valueEntry="betweendate" 
		///                  sqlTemplate="(#FIELD# &gt;= #betweendate_1# AND #FIELD# &lt;= #betweendate_2#)" />
		///        <operator id="datatype_date_isnotbetween" text="Is Not Between" valueEntry="betweendate" 
		///                  sqlTemplate="NOT (#FIELD# &gt;= #betweendate_1# AND #FIELD# &lt;= #betweendate_2#)" />
		///        <operator id="datatype_date_isonorafter" text="Is On or After" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# &gt;= #onedate_1#" />
		///        <operator id="datatype_date_isonorbefore" text="Is On or Before" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# &lt;= #onedate_1#" />
		///        <operator id="datatype_date_isafter" text="Is After" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# &gt; #onedate_1#" />
		///        <operator id="datatype_date_isbefore" text="Is Before" valueEntry="onedate" 
		///                  sqlTemplate="#FIELD# &lt; #onedate_1#" />
		///        <operator id="datatype_date_isnull" text="Is Null" valueEntry="blank" 
		///                  sqlTemplate="#FIELD# IS NULL" />
		///        <operator id="datatype_date_isnotnull" text="Is Not Null" valueEntry="blank" 
		///                  sqlTemplate="#FIELD# IS NOT NULL" />
		///    </operatorList>
		///
		///</configuration>
		/// ]]>
		/// </code>
		/// </example>
		/// <seealso cref="SqlWhereBuilderOperator">SqlWhereBuilderOperator class</seealso>
		/// <seealso cref="SqlWhereBuilderOperatorsCollection">SqlWhereBuilderOperatorsCollection class</seealso>
		[Browsable(true), Category("Configuration")]
		public string OperatorListsFile
		{
			get{return _operatorListsFile;}
			set{_operatorListsFile = value;}
		}


		/// <summary>
		/// XML-based configuration file for defining Fields
		/// </summary>
		/// <remarks>
		/// <para>Set this property to the url of an xml file which defines the 
		/// fields used by this SqlWhereBuilder.  Each included field
		/// becomes an available dropdown selection for the user, and may
		/// be added as conditions.  A Field is defined using the &lt;field&gt;
		/// tag with attributes defining the field's ID (database name),
		/// display text, and the ID of an associated OperatorList.</para>
		/// <para>
		/// An additional attribute, <i>parameterDataType</i> should be
		/// used if the WHERE clause should be generated with parameters.
		/// The value of <i>parameterDataType</i> should be one of the values from the
		/// <see cref="System.Data.DbType">System.Data.DbType</see> enum.
		/// </para>
		/// </remarks>
		/// <example>
		/// The following example shows a Fields configuration file based
		/// on the Orders table of the Northwind database.  The attribute
		/// <i>parameterDataType</i> is included to allow a WHERE clause to
		/// be genereated with parameters.
		/// <code>
		/// <![CDATA[
		///<configuration>
		///    <field id="CustomerID"     text="Customer"         
		///           operatorList="customer"         parameterDataType="String" />
		///    <field id="EmployeeID"     text="Employee"         
		///           operatorList="employee"         parameterDataType="String" />
		///    <field id="OrderDate"      text="Order Date"       
		///           operatorList="datatype_date"    parameterDataType="Date" />
		///    <field id="RequiredDate"   text="Required Date"    
		///           operatorList="datatype_date"    parameterDataType="Date" />
		///    <field id="ShippedDate"    text="Shipped Date"     
		///           operatorList="datatype_date"    parameterDataType="Date" />
		///    <field id="ShipVia"        text="Shipped"          
		///           operatorList="shipper"          parameterDataType="Int16" />
		///    <field id="Freight"        text="Freight"          
		///           operatorList="datatype_numeric" parameterDataType="Decimal" />
		///    <field id="ShipName"       text="Ship Name"        
		///           operatorList="datatype_text"    parameterDataType="String" />
		///    <field id="ShipAddress"    text="Ship Address"     
		///           operatorList="datatype_text"    parameterDataType="String" />
		///    <field id="ShipCity"       text="Ship City"        
		///           operatorList="datatype_text"    parameterDataType="String" />
		///    <field id="ShipPostalCode" text="Ship Postal Code" 
		///           operatorList="datatype_text"    parameterDataType="String" />
		///    <field id="ShipCountry"    text="Ship Country"     
		///           operatorList="datatype_text"    parameterDataType="String" />
		///</configuration>
		/// ]]>
		/// </code>
		/// </example>
		[Browsable(true), Category("Configuration")]
		public string FieldsFile
		{
			get{return _fieldsFile;}
			set{_fieldsFile = value;}
		}

		#endregion

		#region Style Support

		/************************************************************************
		 * style display defaults
		 ************************************************************************/
		private const string sqlwb_DISPLAY_TABLE_CLASS     = "";
		private const string sqlwb_DISPLAY_TABLE_STYLE     = "border: 1px solid #999999;";	      // style attribute for display (outer-most) table
		private const string sqlwb_DISPLAY_TABLE_PADDING   = "4";                                  // padding attribute for display table
		private const string sqlwb_DISPLAY_TABLE_SPACING   = "0";                                 // spacing attribute for display table
		private const string sqlwb_DISPLAY_TABLE_BORDER    = "0";                                  // border attribute for display table
		private const string sqlwb_FORM_COLOR              = "#CCCCCC";                           // condition entry form background color
		private const string sqlwb_BUTTON_CLASS            = "";
		private const string sqlwb_BUTTON_STYLE            = "font-size: 8pt;";                   // style attribute for the condition buttons
		private const string sqlwb_ADDBUTTON_TEXT          = "Add";                               // text for the condition Add button
		private const string sqlwb_UPDATEBUTTON_TEXT       = "Update";                            // text for the condition Update button
		private const string sqlwb_CANCELBUTTON_TEXT       = "Cancel";                            // text for the condition Cancel button 
		private const string sqlwb_EDITBUTTONS_CLASS       = "";
		private const string sqlwb_EDITBUTTONS_STYLE       = "font-size: 10pt; cursor:pointer;";   // style for the delete & edit buttons
		private const string sqlwb_EDITBUTTONS_COLOR       = "#CCCCCC";                           // normal background color for delete & edit buttons
		private const string sqlwb_EDITBUTTONS_HILITECOLOR = "#999999";                           // hilite background color for delete & edit buttons
		private const string sqlwb_EDITBUTTON_TEXT         = "&nbsp;>>&nbsp;";                    // text for the condition edit button
		private const string sqlwb_DELETEBUTTON_TEXT       = "&nbsp;X&nbsp;";                     // text for the condition delete button
		private const string sqlwb_CONDITIONDISPLAY_CLASS  = "";
		private const string sqlwb_CONDITIONDISPLAY_STYLE  = "font-size: 10pt; padding: 2px;";    // style for the display of a single condition
		private const string sqlwb_CONDITIONCELL_CLASS     = "";
		private const string sqlwb_CONDITIONCELL_STYLE     = "border: 1px solid #CCCCCC; padding:2px;"; // style for the cell of a single condition
		private const string sqlwb_NOCONDITIONS_TEXT       = "No conditions specified.";           // text to display when no conditions have been added
		private const string sqlwb_NOCONDITIONS_STYLE      = "font-size: 10pt;";                   // style to display when no conditions have been added
		private const string sqlwb_NOCONDITIONS_CLASS      = "";


		// private members supporting styles
		private string _mainClass               = sqlwb_DISPLAY_TABLE_CLASS;
		private string _mainStyle				= sqlwb_DISPLAY_TABLE_STYLE;
		private string _padding                 = sqlwb_DISPLAY_TABLE_PADDING;
		private string _spacing                 = sqlwb_DISPLAY_TABLE_SPACING;
		private string _border                  = sqlwb_DISPLAY_TABLE_BORDER;
		private string _formColor               = sqlwb_FORM_COLOR;
		private string _buttonClass             = sqlwb_BUTTON_CLASS;
		private string _buttonStyle             = sqlwb_BUTTON_STYLE;
		private string _addButtonText           = sqlwb_ADDBUTTON_TEXT;
		private string _updateButtonText        = sqlwb_UPDATEBUTTON_TEXT;
		private string _cancelButtonText        = sqlwb_CANCELBUTTON_TEXT;
		private string _editButtonsClass        = sqlwb_EDITBUTTONS_CLASS;
		private string _editButtonsStyle        = sqlwb_EDITBUTTONS_STYLE;
		private string _editButtonsColor        = sqlwb_EDITBUTTONS_COLOR;
		private string _editButtonsHiliteColor  = sqlwb_EDITBUTTONS_HILITECOLOR;
		private string _editButtonText          = sqlwb_EDITBUTTON_TEXT;
		private string _deleteButtonText        = sqlwb_DELETEBUTTON_TEXT;
		private string _conditionDisplayClass   = sqlwb_CONDITIONDISPLAY_CLASS;
		private string _conditionDisplayStyle   = sqlwb_CONDITIONDISPLAY_STYLE;
		private string _conditionCellClass      = sqlwb_CONDITIONCELL_CLASS;
		private string _conditionCellStyle      = sqlwb_CONDITIONCELL_STYLE;
		private string _noConditionsClass       = sqlwb_NOCONDITIONS_CLASS;
		private string _noConditionsStyle       = sqlwb_NOCONDITIONS_STYLE;
		private string _noConditionsText        = sqlwb_NOCONDITIONS_TEXT;


		/// <summary>
		/// CSS class attribute for the outer display table
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DISPLAY_TABLE_CLASS),
		Description("CSS Class for the outer display table")]
		public string MainClass               { get {return _mainClass             ;}  set {_mainClass              = value; }   }                                  

		/// <summary>
		/// CSS style attribute for the outer display table
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DISPLAY_TABLE_STYLE),
		Description("CSS Style for the outer display table")]
		public string MainStyle               { get {return _mainStyle             ;}  set {_mainStyle              = value; }   }                 

		/// <summary>
		/// Cellpadding attribute for the outer display table
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DISPLAY_TABLE_PADDING),
		Description("Cellpadding attribute for the outer display table")]
		public string Padding                 { get {return _padding               ;}  set {_padding                = value; }   }                 

		/// <summary>
		/// Cellspacing attribute for the outer display table
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DISPLAY_TABLE_SPACING),
		Description("Cellspacing attribute for the outer display table")]
		public string Spacing                 { get {return _spacing               ;}  set {_spacing                = value; }   }                 

		/// <summary>
		/// Border attribute for the outer display table
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DISPLAY_TABLE_BORDER),
		Description("Border attribute for the outer display table")]
		public string Border                  { get {return _border                ;}  set {_border                 = value; }   }                 

		/// <summary>
		/// Background color for the condition entry form
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_FORM_COLOR),
		Description("Background color for the condition entry form")]
		public string FormColor               { get {return _formColor             ;}  set {_formColor              = value; }   }                 

		/// <summary>
		/// CSS class attribute for the Add, Update, and Cancel buttons
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_BUTTON_CLASS),
		Description("CSS Class for the Add, Update, and Cancel buttons")]
		public string ButtonClass             { get {return _buttonClass           ;}  set {_buttonClass            = value; }   }                 

		/// <summary>
		/// CSS style attribute for the Add, Update, and Cancel buttons
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_BUTTON_STYLE),
		Description("CSS Style for the Add, Update, and Cancel buttons")]
		public string ButtonStyle             { get {return _buttonStyle           ;}  set {_buttonStyle            = value; }   }                 

		/// <summary>
		/// Display text for the Add button
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_ADDBUTTON_TEXT),
		Description("Display text for the Add button")]
		public string AddButtonText           { get {return _addButtonText         ;}  set {_addButtonText          = value; }   }                 

		/// <summary>
		/// Display text for the Update button
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_UPDATEBUTTON_TEXT),
		Description("Display text for the Update button")]
		public string UpdateButtonText        { get {return _updateButtonText      ;}  set {_updateButtonText       = value; }   }                 

		/// <summary>
		/// Display text for the Cancel button
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_CANCELBUTTON_TEXT),
		Description("Display text for the Cancel button")]
		public string CancelButtonText        { get {return _cancelButtonText      ;}  set {_cancelButtonText       = value; }   }                 

		/// <summary>
		/// CSS class attribute for the table cells representing Edit and Delete buttons
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_EDITBUTTONS_CLASS),
		Description("CSS Class for the table cells representing Edit and Delete buttons")]
		public string EditButtonsClass        { get {return _editButtonsClass      ;}  set {_editButtonsClass       = value; }   }                 

		/// <summary>
		/// CSS Style for the table cells representing Edit and Delete buttons
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_EDITBUTTONS_STYLE),
		Description("CSS Style for the table cells representing Edit and Delete buttons")]
		public string EditButtonsStyle        { get {return _editButtonsStyle      ;}  set {_editButtonsStyle       = value; }   }                 

		/// <summary>
		/// Background color for the table cells representing Edit and Delete buttons
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_EDITBUTTONS_COLOR),
		Description("Background color for the table cells representing Edit and Delete buttons")]
		public string EditButtonsColor        { get {return _editButtonsColor      ;}  set {_editButtonsColor       = value; }   }                 

		/// <summary>
		/// Background color for the table cells represneting Edit and Delete buttons when hilited
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_EDITBUTTONS_HILITECOLOR),
		Description("Background color for the table cells represneting Edit and Delete buttons when hilited")]
		public string EditButtonsHiliteColor  { get {return _editButtonsHiliteColor;}  set {_editButtonsHiliteColor = value; }   }                 

		/// <summary>
		/// Display text for the table cell representing the Edit button
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_EDITBUTTON_TEXT),
		Description("Display text for the table cell representing the Edit button")]
		public string EditButtonText          { get {return _editButtonText        ;}  set {_editButtonText         = value; }   }                 

		/// <summary>
		/// Display text for the table cell representing the Delete button
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_DELETEBUTTON_TEXT),
		Description("Display text for the table cell representing the Delete button")]
		public string DeleteButtonText        { get {return _deleteButtonText      ;}  set {_deleteButtonText       = value; }   }                 

		/// <summary>
		/// CSS class attribute for the display text for conditions
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_CONDITIONDISPLAY_CLASS),
		Description("CSS Class for the display text for conditions")]
		public string ConditionDisplayClass   { get {return _conditionDisplayClass ;}  set {_conditionDisplayClass  = value; }   }                 

		/// <summary>
		/// CSS style attribute for the display text for conditions
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_CONDITIONDISPLAY_STYLE),
		Description("CSS Style for the display text for conditions")]
		public string ConditionDisplayStyle   { get {return _conditionDisplayStyle ;}  set {_conditionDisplayStyle  = value; }   }                 

		/// <summary>
		/// CSS class attribute for table cells in which conditions are displayed
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_CONDITIONCELL_CLASS),
		Description("CSS Class for table cells in which conditions are displayed")]
		public string ConditionCellClass      { get {return _conditionCellClass    ;}  set {_conditionCellClass     = value; }   }                 

		/// <summary>
		/// CSS style attribute for table cells in which conditions are displayed
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_CONDITIONCELL_STYLE),
		Description("CSS Style for table cells in which conditions are displayed")]
		public string ConditionCellStyle      { get {return _conditionCellStyle    ;}  set {_conditionCellStyle     = value; }   }                 

		/// <summary>
		/// CSS class attribute for display text when no conditions are present
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_NOCONDITIONS_CLASS),
		Description("CSS Class for display text when no conditions are present")]
		public string NoConditionsClass       { get {return _noConditionsClass     ;}  set {_noConditionsClass      = value; }   }                 

		/// <summary>
		/// CSS style attribute for display text when no conditions are present
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_NOCONDITIONS_STYLE),
		Description("CSS Style for display text when no conditions are present")]
		public string NoConditionsStyle       { get {return _noConditionsStyle     ;}  set {_noConditionsStyle      = value; }   }                 

		/// <summary>
		/// Text to display when no conditions are present
		/// </summary>
		[Category("Appearance"), 
		DefaultValue(sqlwb_NOCONDITIONS_TEXT),
		Description("Text to display when no conditions are present")]
		public string NoConditionsText        { get {return _noConditionsText      ;}  set {_noConditionsText       = value; }   }                 

		#endregion

		#region Utility functions

		/// <summary>
		/// Function using regular expressions for a case-insensitive string replace
		/// </summary>
		/// <param name="source">the source string</param>
		/// <param name="replace">text within source to be replaced</param>
		/// <param name="with">text to use as the replacement</param>
		/// <returns>the string with case-insensitive replacements made</returns>
		private string InsensitiveReplace(string source, string replace, string with)
		{
			Regex r = new Regex(replace, RegexOptions.IgnoreCase);
			return r.Replace(source, with);
		}

		
		/// <summary>
		/// Utility function to return a SqlWhereBuilderField object
		/// in this SqlWhereBuilder given its id.
		/// </summary>
		/// <param name="fieldId">the SqlWhereBuilderField's Id property</param>
		/// <returns>the SqlWhereBuilderField object if found, or null if not</returns>
		protected SqlWhereBuilderField GetFieldById(string fieldId)
		{
			for (int i=0; i<this.Fields.Count; i++)
			{
				SqlWhereBuilderField f = this.Fields[i] as SqlWhereBuilderField;
				if (f != null && f.Id == fieldId)
					return f;
			}
			return null;
		}

		/// <summary>
		/// Utility function to return a SqlWhereBuilderOperatorsCollection
		/// object in this SqlWhereBuilder given its id.
		/// </summary>
		/// <param name="opListId">the string id for the SqlWhereBuilderOperatorsCollection</param>
		/// <returns>the SqlWhereBuilderOperatorsCollection object if found, or null if not</returns>
		protected SqlWhereBuilderOperatorsCollection GetOperatorListById(string opListId)
		{
			for (int i=0; i<this.OperatorLists.Count; i++)
			{
				SqlWhereBuilderOperatorsCollection c = this.OperatorLists[i] as SqlWhereBuilderOperatorsCollection;
				if (c != null && c.Id == opListId)
					return c;
			}
			return null;
		}

		/// <summary>
		/// Utility function to return a SqlWhereBuilderOperator object
		/// in this SqlWhereBuilder, given a string field id and operator
		/// id.
		/// </summary>
		/// <param name="fieldId">the id of a Field whose OperatorsList contains the desired Operator</param>
		/// <param name="operatorId">the id of the desired Operator</param>
		/// <returns>a SqlWhereBuilderOperator object if found, or null if not found</returns>
		protected SqlWhereBuilderOperator GetOperatorById(string fieldId, string operatorId)
		{
			SqlWhereBuilderField f = this.GetFieldById(fieldId);

			if (f != null)
			{
				SqlWhereBuilderOperatorsCollection c = this.GetOperatorListById(f.OperatorListId);
				if (c != null)
				{
					foreach(SqlWhereBuilderOperator op in c)
					{
						if (op.Id == operatorId)
							return op;
					}
				}
			}

			return null;
		}

		#endregion
		
		#region Client Javascript Generation

		/// <summary>
		/// Generates a string containing a &lt;script&gt; tag identifying the
		/// client-side javascript code to include on the page; this string
		/// is registered with the page when a SqlWhereBuilder control is present.
		/// </summary>
		/// <returns>the &lt;script&gt; tag in a string</returns>
		protected string ClientJavascriptCodeScript()
		{
			string sSrc = this.ClientCodeLocation;
			string sTemplate = "<script language='JavaScript1.2' src='{0}' ></script>\n";
			return string.Format(sTemplate, sSrc);
		}

		/// <summary>
		/// Returns a consistent identifier based on this SqlWhereBuilder's unique
		/// ID, used when rendering client-side javascript code
		/// </summary>
		/// <param name="additional">additional text to add to the identifier</param>
		/// <returns>a string identifier</returns>
		protected string GetID(string additional)
		{
			if (additional.Length > 0)
				return string.Format("{0}_{1}", this.ClientID, additional);
			else
				return this.ClientID;
		}

		/// <summary>
		/// Returns a consistent identifier based on this SqlWhereBuilder's unique
		/// ID, used when rendering client-side javascript code
		/// </summary>
		/// <returns>a string identifier</returns>
		protected string GetID()
		{
			return this.GetID("");
		}

		/// <summary>
		/// Returns a consistent template for a client-side 
		/// &lt;script&gt; tag
		/// </summary>
		/// <returns>the string</returns>
		protected string GetScriptTemplate()
		{
			return "<script language='JavaScript1.2'>{0}</script>\n";
		}
		
		/// <summary>
		/// Returns a string of javascript code to create
		/// and initialize a SqlWhereBuilder object on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientInitializeScript()
		{
			// get the consistent id
			string sId = this.GetID();

			// client code to construct the object in the proper <div>
			string sConstruct = string.Format(
				"var {0} = new SQLWB_SqlWhereBuilder(\"{0}\");"
				, sId
				);

			// override class/style defaults using server control properties
			string sStyles = ClientDefineStyles();

			// client code to initialize
			string sInitialize = string.Format(
				"{0}.Initialize();"
				, sId
				);

			// put it all together
			return sConstruct + "\n" + sStyles + "\n" + sInitialize + "\n\n";
		}

		/// <summary>
		/// Returns a string of javascript code to create
		/// and initialize the data entry form used by a 
		/// SqlWhereBuilder object on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientInitializeFormScript()
		{
			// get the consistent id
			string sId = this.GetID();

			// client code to construct the object in the proper <div>
			return string.Format(
				"{0}.InitializeForm();\n{0}.UpdateConditionsDisplay();\n"
				, sId
				);
		}

		/// <summary>
		/// Returns a string of javascript code to create
		/// operator lists on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientDefineOperatorLists()
		{
			// loop through the OperatorLists array, preparing
			// the javascript that will instruct the client to define them

			string sId = this.GetID();
			string sOpTemplate 
				= "new SQLWB_Operator(\"{0}\", \"{1}\", \"{2}\", \"{3}\")\n";
			string sListTemplate
				= "{0}.DefineOperatorList(\"{1}\", [\n{2}\n] );\n\n";

			string sOps = "";
			string sLists = "";

			foreach (SqlWhereBuilderOperatorsCollection ol in this.OperatorLists)
			{
				sOps = "";
				foreach (SqlWhereBuilderOperator op in ol)
				{
					if (sOps.Length > 0) sOps += ", ";
					sOps += string.Format(
						sOpTemplate, op.Id, op.Text, op.ValueEntryDiv, op.SqlTemplate);
		
				}
				sLists += string.Format(
					sListTemplate, sId, ol.Id, sOps);
			}

			return sLists;

		}

		/// <summary>
		/// Returns a string of javascript code to create
		/// the fields used by a SqlWhereBuilder object on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientDefineFields()
		{
			string sId = this.GetID();
			string sTemplate 
				= "{0}.DefineField(\"{1}\", \"{2}\", \"{3}\");\n";
			string sFields = "";
			foreach (SqlWhereBuilderField f in this.Fields)
			{
				sFields += string.Format(
					sTemplate, sId, f.Id, f.Text, f.OperatorListId);
			}

			return sFields + "\n";
		}

		/// <summary>
		/// Returns a string of javascript code to create
		/// any initial conditions which should be displayed
		/// by a SqlWhereBuilder object on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientDefineConditions()
		{
			string sId = this.GetID();
			string sCondTemplate
				= "{0}.AddCondition(new SQLWB_Condition(\n"
				+ "    {0}.GetFieldById(\"{1}\")\n"
				+ "   ,{0}.GetOperatorById(\"{1}\", \"{2}\")\n"
				+ "   ,[\n"
				+ "    {3}\n"
				+ "    ], \"{4}\" ) );\n";

			string sValueTemplate
				= "new SQLWB_Value(\"{0}\", \"{1}\", \"{2}\")";


			string sConditions = "";
			string sValues = "";
			foreach (SqlWhereBuilderCondition c in this.Conditions)
			{
				sValues = "";
				foreach (SqlWhereBuilderValue v in c.Values)
				{
					if (sValues.Length > 0) sValues += "\n,   "; else sValues += "   ";
					sValues += string.Format(
						sValueTemplate, v.Name, v.Value.Replace("\"", "\\\""), v.FriendlyValue.Replace("\"", "\\\""));
				}
				
				sConditions += string.Format(
					sCondTemplate, sId, c.FieldId, c.OperatorId, sValues, c.AndOr);
				
			}

			return sConditions + "\n";
		}


		/// <summary>
		/// Returns a string of javascript code to establish style
		/// properties for a SqlWhereBuilder object on the client;
		/// this script is registered with the Page.
		/// </summary>
		/// <returns>the javascript code</returns>
		protected string ClientDefineStyles()
		{
			string sId = this.GetID();
			string sTemplate = "";
			string sStyles = "";

			sTemplate = "{0}.mainClass               = \"{1}\";\n"     
				+ "{0}.mainStyle               = \"{2}\";\n"     
				+ "{0}.padding                 = \"{3}\";\n"     
				+ "{0}.spacing                 = \"{4}\";\n"     
				+ "{0}.border                  = \"{5}\";\n"     
				+ "{0}.formColor               = \"{6}\";\n"     
				+ "{0}.buttonsClass            = \"{7}\";\n"     
				+ "{0}.buttonsStyle            = \"{8}\";\n"     
				+ "{0}.addButtonText           = \"{9}\";\n"     
				+ "{0}.updateButtonText        = \"{10}\";\n"    
				+ "{0}.cancelButtonText        = \"{11}\";\n"    
				+ "{0}.editButtonsClass        = \"{12}\";\n"    
				+ "{0}.editButtonsStyle        = \"{13}\";\n"    
				+ "{0}.editButtonsColor        = \"{14}\";\n"    
				+ "{0}.editButtonsHiliteColor  = \"{15}\";\n"    
				+ "{0}.editButtonText          = \"{16}\";\n"    
				+ "{0}.deleteButtonText        = \"{17}\";\n"    
				+ "{0}.conditionDisplayClass   = \"{18}\";\n"    
				+ "{0}.conditionDisplayStyle   = \"{19}\";\n"    
				+ "{0}.conditionCellClass      = \"{20}\";\n"    
				+ "{0}.conditionCellStyle      = \"{21}\";\n"    
				+ "{0}.noConditionsClass       = \"{22}\";\n"    
				+ "{0}.noConditionsStyle       = \"{23}\";\n"    
				+ "{0}.noConditionsText        = \"{24}\";\n";  
          
 
			sStyles = string.Format(sTemplate, sId
                        ,this.MainClass             
                        ,this.MainStyle             
                        ,this.Padding               
                        ,this.Spacing               
                        ,this.Border                
                        ,this.FormColor             
                        ,this.ButtonClass           
                        ,this.ButtonStyle           
                        ,this.AddButtonText         
                        ,this.UpdateButtonText      
                        ,this.CancelButtonText      
                        ,this.EditButtonsClass      
                        ,this.EditButtonsStyle      
                        ,this.EditButtonsColor      
                        ,this.EditButtonsHiliteColor
                        ,this.EditButtonText        
                        ,this.DeleteButtonText      
                        ,this.ConditionDisplayClass 
                        ,this.ConditionDisplayStyle 
                        ,this.ConditionCellClass    
                        ,this.ConditionCellStyle    
                        ,this.NoConditionsClass     
                        ,this.NoConditionsStyle     
                        ,this.NoConditionsText   
                        );
		
			return sStyles + "\n";
            
		}


		#endregion

		#region Rendering and preparation for rendering

		/// <summary>
		/// Generate child &lt;div&gt; controls for each 
		/// ValueEntryDiv object in this SqlWhereBuilder control.
		/// </summary>
		protected void PrepareValueEntryDivs()
		{
			// prepare the value entry divs as child controls
			this.Controls.Clear();

			foreach (SqlWhereBuilderValueEntryDiv v in this.ValueEntryDivs)
			{
				this.Controls.Add(
					new LiteralControl("<div id=\"" + v.Id + "\" style=\"display: none;\">\n")
					);

				if (v.IsUserControl)
				{
					Control c = this.Page.LoadControl(v.Contents);
					c.ID = v.Id;

					this.Controls.Add(c);
				}
				else
				{
					this.Controls.Add(
						new LiteralControl(v.Contents)
						);
				}

				this.Controls.Add(
					new LiteralControl("</div>\n")
					);

			}
		}


		/// <summary>
		/// Prepare client-side javascript
		/// and child controls, and register code blocks with the Page.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			// prepare client javascript for rendering

			// make sure we have at least one field in the Fields list
			if (this.Fields.Count > 0)
			{

				// we will tell the page that we require postback information
				this.Page.RegisterRequiresPostBack(this);

				// the main Javascript object should be rendered only
				// once no matter how many controls need it.
				this.Page.RegisterClientScriptBlock("SQLWB"
					, ClientJavascriptCodeScript());

				// control-specific blocks - output javascript to initialize the client control
				string sInitialize = ClientInitializeScript();

				// output javascript to define each OperatorList object
				string sOpsLists = ClientDefineOperatorLists();

				// output javascript to define each Field object
				string sFields = ClientDefineFields();

				// output javascript to set up the initial set of conditions
				// as necessary
				string sConditions = ClientDefineConditions();

				// last step - output client code to initialize the conditions form
				// and update the conditions display
				string sInitForm = ClientInitializeFormScript();

				// put it all together & register with the page
				string sFinal = string.Format(
					this.GetScriptTemplate(), 
					sInitialize + sOpsLists + sFields + sConditions
					+ sInitForm);

				this.Page.RegisterStartupScript(
					this.GetID("Initialize"), sFinal);

				// with all the javascript rendered, prepare the ValueEntryDivs
				// for rendering as child controls
				this.PrepareValueEntryDivs();

			}
		}


		/// <summary>
		/// Render this control to the output parameter specified.
		/// </summary>
		/// <param name="output"> The HTML writer to write out to </param>
		protected override void Render(HtmlTextWriter output)
		{
			// if we're ready for rendering but we don't have a field,
			// throw an exception
			if (this.Fields.Count == 0)
				throw new SqlWhereBuilderException(string.Format(
					kEXCEPTION_NOFIELDS, this.ID));

			// also, check each field and its corresponding operatorList;
			// make sure that the operatorList exists and that the
			// valueEntryDivs for each operator exists as well
			foreach(SqlWhereBuilderField f in this.Fields)
			{
				SqlWhereBuilderOperatorsCollection opList = this.GetOperatorListById(f.OperatorListId);
				if (opList == null)
					throw new SqlWhereBuilderException(string.Format(
						kEXCEPTION_MISSINGOPLIST, this.ID, f.Id, f.OperatorListId) );
				else
				{
					foreach(SqlWhereBuilderOperator op in opList)
					{
						if (!this.ValueEntryDivs.Contains(op.ValueEntryDiv))
							throw new SqlWhereBuilderException(string.Format(
								kEXCEPTION_MISSINGVALUEENTRYDIV, this.ID, f.OperatorListId, op.Id, op.ValueEntryDiv) );

					}
				}
			}

			// finally, validate the conditions; throw an exception if a condition field or operator
			// doesn't exist
			foreach(SqlWhereBuilderCondition c in this.Conditions)
			{
				SqlWhereBuilderField f = this.GetFieldById(c.FieldId);
				if (f == null)
					throw new SqlWhereBuilderException(string.Format(
						kEXCEPTION_FIELDDOESNTEXIST, this.ID, c.FieldId) );
				else
				{
					// locate the operator specified
					SqlWhereBuilderOperator op = this.GetOperatorById(c.FieldId, c.OperatorId);
					if (op == null)
						throw new SqlWhereBuilderException(string.Format(
							kEXCEPTION_OPDOESNTEXIST, this.ID, c.OperatorId ) );
				}
			}

			// output the main <div> tag for the client control
			output.Write(string.Format(
				"<div id=\"{0}\" name==\"{0}\"></div>\n"
				, this.GetID("")
				));

			// output the hidden input that allows for the communication
			// between the client object and ASP.NET
			string sCond = this.GetID(kHIDDEN_CONDITIONS);

			output.Write(string.Format(
				"<input type=\"hidden\" id=\"{0}\" name=\"{0}\"/>\n"
				, sCond
				));

			// output all valueEntry <div>s
			this.RenderChildren(output);

		}

		#endregion

		#region Support for XML-based configuration

		/// <summary>
		/// Initialize the SqlWhereBuilder by parsing XML-based 
		/// configuration files for ValueEntryDivs, OperatorLists, 
		/// and Fields, if supplied.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);

			// if this is not a postback, or if we're not tracking view state...
			if (!this.IsTrackingViewState || !this.Page.IsPostBack)
			{
				if (this.ValueEntryFile != "")
					this.LoadValueEntryDivsFromXml(this.ValueEntryFile);

				if (this.OperatorListsFile != "")
					this.LoadOperatorListsFromXml(this.OperatorListsFile);

				if (this.FieldsFile != "")
					this.LoadFieldsFromXml(this.FieldsFile);
			}
		}


		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// ValueEntryDiv objects for each &lt;valueEntry&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="sUrl">string url of the file to parse</param>
		public void LoadValueEntryDivsFromXml(string sUrl)
		{
			XmlDocument x = new XmlDocument();
			try
			{
				x.Load(sUrl);
			}
			catch (Exception)
			{
				x.Load(this.Page.Server.MapPath(sUrl));
			}

			LoadValueEntryDivsFromXml(x);
		}


		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// ValueEntryDiv objects for each &lt;valueEntry&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="x">XmlDocument to parse</param>
		public void LoadValueEntryDivsFromXml(XmlDocument x)
		{
			XmlNodeList nodes = x.SelectNodes("//valueEntry");
			foreach(XmlNode n in nodes)
			{
				if (n.Attributes["id"] != null)
				{
					string sContents;
					bool bIsUserControl = false;

					if (n.Attributes["userControl"] != null)
					{
						bIsUserControl = true;
						sContents = n.Attributes["userControl"].Value;
					}
					else
						sContents = n.InnerXml;

					this.ValueEntryDivs.Add(
						new SqlWhereBuilderValueEntryDiv(
						n.Attributes["id"].Value
						, sContents
						, bIsUserControl)
						);
				}
			}
		}


		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// OperatorList and Operator objects for each &lt;operatorList&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="sUrl">string url of the file to parse</param>
		public void LoadOperatorListsFromXml(string sUrl)
		{
			XmlDocument x = new XmlDocument();
			try
			{
				x.Load(sUrl);
			}
			catch (Exception)
			{
				x.Load(this.Page.Server.MapPath(sUrl));
			}
			LoadOperatorListsFromXml(x);
		}
		

		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// OperatorList and Operator objects for each &lt;operatorList&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="x">XmlDocument to parse</param>
		public void LoadOperatorListsFromXml(XmlDocument x)
		{
			XmlNodeList nodes = x.SelectNodes("//operatorList");
			foreach(XmlNode n in nodes)
			{
				if (n.Attributes["id"] != null)
				{
					SqlWhereBuilderOperatorsCollection c 
						= new SqlWhereBuilderOperatorsCollection(n.Attributes["id"].Value);
					
					XmlNodeList ops = n.SelectNodes("operator");
					foreach(XmlNode op in ops)
					{
						if (op.Attributes["id"] != null)
						{
							SqlWhereBuilderOperator o 
								= new SqlWhereBuilderOperator();
							o.Id = op.Attributes["id"].Value;
							o.Text = (op.Attributes["text"] == null ? "" : op.Attributes["text"].Value);
							o.ValueEntryDiv = (op.Attributes["valueEntry"] == null ? "" : op.Attributes["valueEntry"].Value);
							o.SqlTemplate = (op.Attributes["sqlTemplate"] == null ? "" : op.Attributes["sqlTemplate"].Value);

							c.Add(o);

						}
					}

					this.OperatorLists.Add(c);
				}
			}
		}


		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// Field objects for each &lt;field&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="sUrl">string url of the file to parse</param>
		public void LoadFieldsFromXml(string sUrl)
		{
			XmlDocument x = new XmlDocument();
			try
			{
				x.Load(sUrl);
			}
			catch (Exception)
			{
				x.Load(this.Page.Server.MapPath(sUrl));
			}
			LoadFieldsFromXml(x);
		}
		

		/// <summary>
		/// Parse the given XML-based configuration file, adding
		/// Field objects for each &lt;field&gt; tag
		/// defined in the file.
		/// </summary>
		/// <param name="x">XmlDocument to parse</param>
		public void LoadFieldsFromXml(XmlDocument x)
		{
			XmlNodeList nodes = x.SelectNodes("//field");
			foreach(XmlNode n in nodes)
			{
				if (n.Attributes["id"] != null)
				{
					SqlWhereBuilderField f
						= new SqlWhereBuilderField();
					f.Id = n.Attributes["id"].Value;
					f.Text = (n.Attributes["text"] == null ? "" : n.Attributes["text"].Value);
					f.OperatorListId = (n.Attributes["operatorList"] == null ? "" : n.Attributes["operatorList"].Value);
					f.ParameterDataType = (n.Attributes["parameterDataType"] == null ? System.Data.DbType.String : (System.Data.DbType) System.Enum.Parse(typeof(System.Data.DbType), n.Attributes["parameterDataType"].Value, true));

					this.Fields.Add(f);
				}
			}

		}


		#endregion

		#region Methods for returning the WHERE Clause

		/// <summary>
		/// Returns a string suitable for inclusion in a SQL WHERE
		/// clause, given the current set of conditions
		/// </summary>
		/// <returns>the current conditions in the syntax of a SQL WHERE clause</returns>
		/// <remarks>
		/// This method processes the current set of conditions according to
		/// the <i>sqlTemplate</i> attributes of each condition's operator
		/// to generate a string suitable for inclusion in a SQL WHERE clause.
		/// This method assumes that the <i>sqlTemplate</i>s are set up with 
		/// appropriate syntax (e.g. single quotes delimiting character values)
		/// and not to use parameters objects.
		/// </remarks>
		public string GetWhereClause()
		{
			// loop through conditions, matching up to the operator
			// SqlTemplates, to generate a where clause
			
			string sWhere = "";

			foreach (SqlWhereBuilderCondition c in this.Conditions)
			{
				SqlWhereBuilderOperator op = this.GetOperatorById(c.FieldId, c.OperatorId);

				if (op != null)
				{
					if (sWhere.Length > 0) sWhere += " " + c.AndOr + " ";

					string sCond = op.SqlTemplate;

					// replace the field id
					sCond = InsensitiveReplace(sCond, "#FIELD#", c.FieldId);

					// replace any value entries
					foreach(SqlWhereBuilderValue v in c.Values)
					{
						// replace single quotes with two single quotes
						string sValue = ValidateValue(v.Value);
						sCond = InsensitiveReplace(sCond, "#" + v.Name + "#", sValue);
					}

					sWhere += sCond;

				}
			}
			
			return sWhere;

		}


		/// <summary>
		/// Validate each value supplied with conditions
		/// when a WHERE clause is generated with the
		/// <see cref="GetWhereClause">GetWhereClause</see>
		/// method.
		/// </summary>
		/// <param name="sValue">the value to validate</param>
		/// <returns>the value after validation</returns>
		/// <remarks>
		/// This method is provided to help guard against SQL injection
		/// attacks and is called on each value when the GetWhereClause() 
		/// method is used.  It escapes single quotes with two single quotes
		/// and removes the comment markings
		/// -- and /*.  It is strongly recommended that additional validation
		/// be performed by the developer when using the GetWhereClause() method.
		/// The method may be overriden by an inheriting class if
		/// a custom function is required.
		/// </remarks>
		/// <seealso cref="GetWhereClause">GetWhereClause method</seealso>
		protected virtual string ValidateValue(string sValue)
		{
			// this method may be overriden to provide custom validation
			// of values; this is necessary for mitigating the possibility
			// of SQL injection attacks; an overriden version of this
			// method may wish to throw exceptions; the virtual method
			// just returns a scrubbed version of the passed in value.

			// default behaviors:
			// replace single quotes with two single quotes
			string sReturn = sValue.Replace("'", "''");

			// remove any -- or /* comment markings
			sReturn = sReturn.Replace("--","");
			sReturn = sReturn.Replace("/*", "");

			return sReturn;							
		}


		/// <summary>
		/// Returns a string suitable for inclusion in a SQL WHERE
		/// clause, given the current set of conditions, using parameter
		/// objects.
		/// </summary>
		/// <param name="cmd">the IDbCommand object to which parameters should be added</param>
		/// <returns>a string suitable for inclusing in a SQL WHERE clause</returns>
		/// <remarks>
		/// GetWhereClauseWithParameters() returns a string suitable for
		/// inclusion in a SQL WHERE clause, supplying appropriate placeholders
		/// for parameters and adding appropriate parameter objects to the
		/// supplied IDbCommand.
		/// Use this overloaded method when using SqlCommand, 
		/// OleDbCommand, or OdbcCommand objects.  Appropriate placeholders
		/// are automatically generated.
		/// </remarks>
		public string GetWhereClauseWithParameters(System.Data.IDbCommand cmd)
		{
			return GetWhereClauseWithParameters(cmd, null, null);
		}
		

		/// <summary>
		/// Returns a string suitable for inclusion in a SQL WHERE
		/// clause, given the current set of conditions, using parameter
		/// objects.
		/// </summary>
		/// <param name="cmd">the IDbCommand object to which parameters should be added</param>
		/// <param name="paramPlaceholder">a string template for constructing WHERE clause parameter placeholders</param>
		/// <param name="paramName">a string template for constructing parameter names</param>
		/// <returns>a string suitable for inclusing in a SQL WHERE clause</returns>
		/// <remarks>
		/// GetWhereClauseWithParameters() returns a string suitable for
		/// inclusion in a SQL WHERE clause, supplying appropriate placeholders
		/// for parameters and adding appropriate parameter objects to the
		/// supplied IDbCommand.
		/// Use this overloaded method when using an IDbParameter type that
		/// isn't either the SqlCommand, 
		/// OleDbCommand, or OdbcCommand types.  The <i>paramPlaceholder</i>
		/// and <i>paramName</i> 
		/// arguments should be string.Format style templates, with {0}
		/// included for the parameter number.  
		/// </remarks>
		/// <example>
		/// This example shows how to use the GetWhereClauseWithParameters
		/// with an OracleClient OracleCommmand object.
		/// <code>
		///OracleCommand cmd = new OracleCommand();
		///string sWhere = SqlWhereBuilder1.GetWhereClauseWithParameters(
		///                   cmd, ":param{0}", "param{0}"
		///                   );
		/// </code>                  
		/// </example>
		public string GetWhereClauseWithParameters(System.Data.IDbCommand cmd, string paramPlaceholder, string paramName)
		{
			// construct a WHERE clause given the current set of conditions;
			// assumes we'll be using IDataParameter objects, so the 
			// SqlTemplate properties of each Operator object must be
			// set accordingly; parameters will be added to the given command's
			// parameters collection

			const string kEXCEPTION_IDBCOMMAND_NOTRECOGNIZED = "The IDbCommand passed to the GetWhereClauseWithParameters() method" 
				   + " is not recognized as a SqlCommand, OleDbCommand, or OdbcCommand object."
				   + " The paramPlaceholder and paramName arguments must therefore be supplied.";

			
			string sWhere = "";

			string sParamPlaceholder = paramPlaceholder;
			string sParamName = paramName;

			// if the placeholder template string isn't supplied, attempt to derive it
			if (paramPlaceholder == null || paramPlaceholder == "")
			{
				if (cmd is System.Data.SqlClient.SqlCommand)
				{
					sParamPlaceholder = "@param{0}";
				}
				else if(cmd is System.Data.OleDb.OleDbCommand)
				{
					sParamPlaceholder = "?";
				}
				else if(cmd is System.Data.Odbc.OdbcCommand)
				{
					sParamPlaceholder = "?";
				}
				else throw new SqlWhereBuilderException(
						 kEXCEPTION_IDBCOMMAND_NOTRECOGNIZED);

			}

			// if the name template string isn't supplied, attempt to derive it
			if (paramName == null || paramName == "")
			{
				sParamName = "@param{0}";
			}


			int i = 0;
			foreach (SqlWhereBuilderCondition c in this.Conditions)
			{
				SqlWhereBuilderField f = this.GetFieldById(c.FieldId);
				SqlWhereBuilderOperator op = this.GetOperatorById(c.FieldId, c.OperatorId);

				if ((f != null) && (op != null))
				{
					if (sWhere.Length > 0) sWhere += " " + c.AndOr + " ";

					string sCond = op.SqlTemplate;

					// replace the field id
					sCond = InsensitiveReplace(sCond, "#FIELD#", c.FieldId);

					// look at value entries to construct parameters
					foreach(SqlWhereBuilderValue v in c.Values)
					{
						// construct the parameter
						System.Data.IDbDataParameter p = cmd.CreateParameter();
						p.DbType = f.ParameterDataType;
						p.ParameterName = string.Format(sParamName, i);
						p.Value = v.Value;
						cmd.Parameters.Add(p);

						// replace the value name placeholder with the parameter name
						sCond = InsensitiveReplace(sCond, "#" + v.Name + "#", string.Format(sParamPlaceholder, i));

						i++;
					}

					sWhere += sCond;

				}
			}
				
			return sWhere;
			
		}
		#endregion

		#region IPostBackDataHandler Members


		/// <summary>
		/// Occurs when the set of conditions has changed 
		/// between posts back to the server
		/// </summary>
		public event EventHandler ConditionsChanged;
      
		/// <summary>
		/// Invokes delegates registered with the ConditionsChanged event. 
		/// </summary>
		/// <param name="e"></param>		
		protected virtual void OnConditionsChanged(EventArgs e) 
		{         
			if (ConditionsChanged != null) 
			{
				ConditionsChanged(this, e);
			}   
		}


		/// <summary>
		/// Raises the ConditionsChanged event when conditions have 
		/// changed upon postback.
		/// </summary>
		public void RaisePostDataChangedEvent()
		{
			OnConditionsChanged(new EventArgs());
		}


		/// <summary>
		/// Loads information about conditions posted from the client-side
		/// SqlWhereBuilder object
		/// </summary>
		/// <param name="postDataKey"></param>
		/// <param name="postCollection"></param>
		/// <returns></returns>
		public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
		{
			// get the conditions passed in through the hidden field
			string sHidden = this.GetID(kHIDDEN_CONDITIONS);
			string sData = postCollection[sHidden];

			// the data is escaped on the client end; decode it here
			sData = this.Page.Server.UrlDecode(sData);

			// treat it like a real Xml document and deserialize from there
			XmlDocument x = new XmlDocument();
			x.LoadXml(sData);
			SqlWhereBuilderConditionCollection cNew = new SqlWhereBuilderConditionCollection(x.DocumentElement);

			// test if the conditions have changed;
			bool retValue = !(this.Conditions.Equals(cNew));

			this.Conditions = cNew;

			return retValue;
		}

		#endregion

	}
}
